﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web;

namespace Com.VikingErik.Mvc.ResumingDownloads
{
    public class ResumingController : Controller
    {
        ResumableDownloadRequest ResumableRequestObject = new ResumableDownloadRequest();

        private void CheckForPreconditions(HttpContextBase httpContext)
        {
            var request = httpContext.Request;
            string check;

            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfRange)])))
            {
                ResumableRequestObject.Precondition = new Precondition<string>(PreconditionType.IfRange, check);
            }
            else if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfMatch)])))
            {
                ResumableRequestObject.Precondition = new Precondition<string[]>(PreconditionType.IfMatch, check.Split(','));
            }
            else if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfNoneMatch)])))
            {
                ResumableRequestObject.Precondition = new Precondition<string[]>(PreconditionType.IfNoneMatch, check.Split(','));
            }
            else if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfUnmodifiedSince)])))
            {
                DateTimeOffset parsed;
                if (DateTimeOffset.TryParse(check, out parsed))
                {
                    ResumableRequestObject.Precondition = new Precondition<DateTimeOffset>(PreconditionType.IfUnmodifiedSince, parsed);
                }
            }
            else if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfModifiedSince)])))
            {
                DateTimeOffset parsed;
                if (DateTimeOffset.TryParse(check, out parsed))
                {
                    ResumableRequestObject.Precondition = new Precondition<DateTimeOffset>(PreconditionType.IfModifiedSince, parsed);
                }
            }
        }

        protected virtual void ParseRequestHeaderRanges(HttpContextBase context)
        {
            var request = context.Request;
            string rangeHeader = request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderRange)];

            if (!string.IsNullOrEmpty(rangeHeader))
            {
                // rangeHeader contains the value of the Range HTTP Header and can have values like:
                //      Range: bytes=0-1            * Get bytes 0 and 1, inclusive
                //      Range: bytes=0-500          * Get bytes 0 to 500 (the first 501 bytes), inclusive
                //      Range: bytes=400-1000       * Get bytes 500 to 1000 (501 bytes in total), inclusive
                //      Range: bytes=-200           * Get the last 200 bytes
                //      Range: bytes=500-           * Get all bytes from byte 500 to the end
                //
                // Can also have multiple ranges delimited by commas, as in:
                //      Range: bytes=0-500,600-1000 * Get bytes 0-500 (the first 501 bytes), inclusive plus bytes 600-1000 (401 bytes) inclusive

                // Remove "Ranges" and break up the ranges
                string[] ranges = rangeHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());

                ResumableRequestObject.Ranges = new ByteRange[ranges.Length];

                for (int i = 0; i < ranges.Length; i++)
                {
                    const int START = 0, END = 1;
                    long parsedValue;

                    string[] currentRange = ranges[i].Split("-".ToCharArray());

                    if (string.IsNullOrEmpty(currentRange[END]) || !long.TryParse(currentRange[END], out parsedValue))
                        // No end specified or it's invalid
                        ResumableRequestObject.Ranges[i].EndByte = long.MaxValue;
                    else
                        ResumableRequestObject.Ranges[i].EndByte = parsedValue;


                    if (string.IsNullOrEmpty(currentRange[START]) || !long.TryParse(currentRange[START], out parsedValue))
                    {
                        // No beginning specified, get last n bytes of file
                        ResumableRequestObject.Ranges[i].StartByte = -ResumableRequestObject.Ranges[i].EndByte;
                        ResumableRequestObject.Ranges[i].EndByte = long.MaxValue;
                    }
                    else
                        ResumableRequestObject.Ranges[i].StartByte = parsedValue;
                }
            }
        }

        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.Result is FileResult)
            {
                CheckForPreconditions(filterContext.HttpContext);
                ParseRequestHeaderRanges(filterContext.HttpContext);

                FileContentResult fcr = filterContext.Result as FileContentResult;
                if (null != fcr)
                {
                    filterContext.Result = new ResumableFileContentResult(ResumableRequestObject, fcr.FileContents, fcr.ContentType);
                }
                else
                {
                    FileStreamResult fsr = filterContext.Result as FileStreamResult;
                    if (null != fsr)
                    {
                        filterContext.Result = new ResumableFileStreamResult(ResumableRequestObject, fsr.FileStream, fsr.ContentType);
                    }
                    else
                    {
                        FilePathResult fpr = filterContext.Result as FilePathResult;
                        if (null != fpr)
                        {
                            filterContext.Result = new ResumableFilePathResult(ResumableRequestObject, fpr.FileName, fpr.ContentType);
                        }
                    }
                }
            }

            base.OnActionExecuted(filterContext);
        }
    }
}
